﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using HandyControl.Data;

namespace HandyControl.Controls;

public class ImageBlock : FrameworkElement
{
    private readonly DispatcherTimer _dispatcherTimer;

    private BitmapSource _source;

    private int _indexMax;

    private int _indexMin;

    private int _currentIndex;

    private int _blockWidth;

    private int _blockHeight;

    private bool _isDisposed;

    private int _columns = 1;

    public ImageBlock()
    {
        _dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render)
        {
            Interval = Interval
        };

        IsVisibleChanged += ImageBlock_IsVisibleChanged;
    }

    ~ImageBlock() => Dispose();

    public void Dispose()
    {
        if (_isDisposed) return;

        IsVisibleChanged -= ImageBlock_IsVisibleChanged;
        _dispatcherTimer.Stop();
        _isDisposed = true;

        GC.SuppressFinalize(this);
    }

    private void ImageBlock_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (IsVisible)
        {
            _dispatcherTimer.Tick += DispatcherTimer_Tick;
            if (IsPlaying)
            {
                _dispatcherTimer.Start();
            }
        }
        else
        {
            _dispatcherTimer.Stop();
            _dispatcherTimer.Tick -= DispatcherTimer_Tick;
        }
    }

    private void UpdateDatas()
    {
        if (_source == null) return;

        _indexMin = StartRow * _columns + StartColumn;
        _indexMax = EndRow * _columns + EndColumn;
        _currentIndex = _indexMin;
        _blockWidth = _source.PixelWidth / _columns;
        _blockHeight = _source.PixelHeight / Rows;
    }

    private static void OnPositionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = (ImageBlock) d;

        if (e.Property == ColumnsProperty)
        {
            ctl._columns = (int) e.NewValue;
        }

        ctl.UpdateDatas();
    }

    private void DispatcherTimer_Tick(object sender, EventArgs e) => InvalidateVisual();

    public static readonly DependencyProperty StartColumnProperty = DependencyProperty.Register(
        nameof(StartColumn), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int0Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged));

    public int StartColumn
    {
        get => (int) GetValue(StartColumnProperty);
        set => SetValue(StartColumnProperty, value);
    }

    public static readonly DependencyProperty StartRowProperty = DependencyProperty.Register(
        nameof(StartRow), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int0Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged));

    public int StartRow
    {
        get => (int) GetValue(StartRowProperty);
        set => SetValue(StartRowProperty, value);
    }

    public static readonly DependencyProperty EndColumnProperty = DependencyProperty.Register(
        nameof(EndColumn), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int0Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged));

    public int EndColumn
    {
        get => (int) GetValue(EndColumnProperty);
        set => SetValue(EndColumnProperty, value);
    }

    public static readonly DependencyProperty EndRowProperty = DependencyProperty.Register(
        nameof(EndRow), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int0Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged));

    public int EndRow
    {
        get => (int) GetValue(EndRowProperty);
        set => SetValue(EndRowProperty, value);
    }

    public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register(
        nameof(IsPlaying), typeof(bool), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.FalseBox,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnIsPlayingChanged));

    private static void OnIsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = (ImageBlock) d;
        if ((bool) e.NewValue)
        {
            ctl._dispatcherTimer.Start();
        }
        else
        {
            ctl._dispatcherTimer.Stop();
        }
    }

    public bool IsPlaying
    {
        get => (bool) GetValue(IsPlayingProperty);
        set => SetValue(IsPlayingProperty, ValueBoxes.BooleanBox(value));
    }

    public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register(
        nameof(Columns), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int1Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged), obj => (int) obj >= 1);

    public int Columns
    {
        get => (int) GetValue(ColumnsProperty);
        set => SetValue(ColumnsProperty, value);
    }

    public static readonly DependencyProperty RowsProperty = DependencyProperty.Register(
        nameof(Rows), typeof(int), typeof(ImageBlock), new FrameworkPropertyMetadata(ValueBoxes.Int1Box,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnPositionsChanged), obj => (int) obj >= 1);

    public int Rows
    {
        get => (int) GetValue(RowsProperty);
        set => SetValue(RowsProperty, value);
    }

    public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
        nameof(Interval), typeof(TimeSpan), typeof(ImageBlock), new PropertyMetadata(TimeSpan.FromSeconds(1), OnIntervalChanged));

    private static void OnIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = (ImageBlock) d;
        ctl._dispatcherTimer.Interval = (TimeSpan) e.NewValue;
    }

    public TimeSpan Interval
    {
        get => (TimeSpan) GetValue(IntervalProperty);
        set => SetValue(IntervalProperty, value);
    }

    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
        nameof(Source), typeof(ImageSource), typeof(ImageBlock), new FrameworkPropertyMetadata(default(ImageSource),
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnSourceChanged));

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = (ImageBlock) d;
        ctl._source = e.NewValue as BitmapSource;
        ctl.UpdateDatas();
    }

    public ImageSource Source
    {
        get => (ImageSource) GetValue(SourceProperty);
        set => SetValue(SourceProperty, value);
    }

    protected override void OnRender(DrawingContext dc)
    {
        if (_source == null) return;
        var croppedBitmap = new CroppedBitmap(_source, CalDisplayRect());
        dc.DrawImage(croppedBitmap, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
    }

    private Int32Rect CalDisplayRect()
    {
        if (_currentIndex > _indexMax)
        {
            _currentIndex = _indexMin;
        }

        var x = _currentIndex % _columns * _blockWidth;
        var y = _currentIndex / _columns * _blockHeight;

        var rect = new Int32Rect(x, y, _blockWidth, _blockHeight);
        _currentIndex++;
        return rect;
    }
}
